package com.tools.web.interceptor.quota;

import com.tools.common.exception.InvalidOperationException;
import com.tools.common.object.Note;
import com.tools.common.object.string.StrKit;
import com.tools.common.thread.Timeunit;
import com.tools.web.GlobalKit;
import com.tools.web.WebKit;
import com.tools.web.interceptor.auth.AuthUser;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import java.lang.reflect.Method;

/**
 * 配额限制拦截器
 * 用于拦截业务已成功，但需要限制成功的次数
 * 比如注册功能，一个 IP 天只能注册 10 个账户等
 * 或者，邮箱验证码已发送，但是需要 1 分钟后才能重新发
 * 【注意】：若该拦截器跟身份拦截器一起使用，则它要在身份拦截器之后
 * */
@Note("配额限制拦截器：" +
        "用于拦截业务已成功，但需要限制成功的次数。" +
        "比如注册功能，一个 IP 天只能注册 10 个账户等。" +
        "或者，邮箱验证码已发送，但是需要 1 分钟后才能重新发。" +
        "【注意】：若该拦截器跟身份拦截器一起使用，则它要在身份拦截器之后")
public abstract class QuotaInterceptor implements HandlerInterceptor {

    @Note("缓存 key 的前缀，后续拼接上 uniqueID 作为 key")
    private final String cachePrefix;

    @Note("到达额度后响应给客户端的提示信息")
    private final String responseMessage;

    @Note("没有找到 UniqueID 参数所响应给客户端的异常信息")
    private final String notHasUniqueIDErrorMessage;



    public QuotaInterceptor(QuotaSource source, boolean mustTrue, String errorMessage) {
        if(!mustTrue) throw new IllegalArgumentException(errorMessage);
        if(source == null) throw new NullPointerException("配额拦截器参数配置项包装类实例不能为 null");
        String cachePrefix = source.getCachePrefix();
        if(cachePrefix == null || cachePrefix.isEmpty()) throw new NullPointerException("成功次数存储在缓存中的 key 的前缀不能为空");
        String responseMessage = source.getResponseMessage();
        if(responseMessage == null || responseMessage.isEmpty()) throw new NullPointerException("到达限制后不能响应空的内容给客户端");
        String notHasUniqueIDErrorMessage = source.getNotHasUniqueIDErrorMessage();
        if(notHasUniqueIDErrorMessage == null || notHasUniqueIDErrorMessage.isEmpty()) throw new NullPointerException("缺少 uniqueID 参数时不能响应空的内容给客户端");
        this.cachePrefix = cachePrefix;
        this.responseMessage = responseMessage;
        this.notHasUniqueIDErrorMessage = notHasUniqueIDErrorMessage;
    }



    /* **************************************************************************************
     *
     *          需要子类实现的功能抽象方法
     *
     * **************************************************************************************
     * */

    @Note("set 业务已成功的次数进缓存里，并设置过期时间。" +
            "在完成业务后必须调用该方法，否则无法保证操作的原子性")
    public abstract void incrSuccessfulCount(String uniqueID, String action, long createExpiration, Timeunit createTimeunit);

    @Note("从缓存中获取业务已经成功的次数，注意这个缓存必须要有过期时间的")
    protected abstract Integer protected_getSuccessfulCount(String key);

    @Note("从缓存中获取该 key 的剩余时间")
    protected abstract long protected_getRemainder(String key);

    protected String protected_getCachePrefix() {
        return cachePrefix;
    }

    /* **************************************************************************************
     *
     *          拦截 + 校验逻辑
     *
     * **************************************************************************************
     * */


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof ResourceHttpRequestHandler) return true;
        HandlerMethod hm = (HandlerMethod) handler;
        Method method = hm.getMethod();
        QuotaLimit quotaLimit = method.getAnnotation(QuotaLimit.class);
        if(quotaLimit == null) {
            Class<?> typeClass = method.getDeclaringClass();
            QuotaLimit typeLimit = typeClass.getAnnotation(QuotaLimit.class);
            return typeLimit == null || private_checkIsAllow(typeLimit, request, response);
        }
        return private_checkIsAllow(quotaLimit, request, response);
    }


    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("校验是否放行")
    private boolean private_checkIsAllow(QuotaLimit quotaLimit, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String uniqueID = private_getUniqueID(quotaLimit, request, response);
        if(uniqueID == null) {
            return false;
        }
        String action = quotaLimit.action();
        String key = this.cachePrefix + action + ':' + uniqueID;
        Integer successfulCount = this.protected_getSuccessfulCount(key);
        //还没成功过，那就放行
        if(successfulCount == null) {
            return true;
        }
        int allow = quotaLimit.allow();
        //还没到配额的总大小，再次放行
        if(successfulCount < allow) {
            return true;
        }
        long ttl = this.protected_getRemainder(key);
        //刚好过期，同样放行
        if(ttl < 1) {
            return true;
        }
        //已经达到配额的总大小了，不允许再成功
        response.getWriter().write(this.private_getErrorThrowMsg(quotaLimit, uniqueID));
        return false;
    }


    @Note("从请求参数/IP/Request Attribute 里获取唯一标识作为 key 的后缀")
    private String private_getUniqueID(QuotaLimit quotaLimit, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String name = quotaLimit.name();
        String uniqueID;
        if("$IP".equals(name)) {
            uniqueID = WebKit.getClientIP();
        } else if("$ID".equals(name)) {
            AuthUser authUser = GlobalKit.getAuthUser(request);
            if(authUser == null) throw new InvalidOperationException("配额限制拦截器的执行顺序应该排在 AuthenticationInterceptor 身份验证拦截器之后");
            uniqueID = String.valueOf(authUser.getUserId());
        } else {
            uniqueID = request.getParameter(name);
        }
        if(StrKit.isBlank(uniqueID)) {
            response.getWriter().write(this.private_getNotHasUniqueIDErrorThrowMsg(quotaLimit, uniqueID));
            return null;
        }
        return uniqueID;
    }


    @Note("获取注解自己配置的错误返回信息，如果是默认值那就返回 null")
    private String private_getErrorThrowMsg(QuotaLimit limit, String uniqueID) {
        int code = limit.code();
        String msg = limit.msg();
        if(code <= 0 || msg.isEmpty()) {
            return this.responseMessage.replace("#{uniqueID}", uniqueID);
        }
        return "{\"code\":" + code + ", \"message\":\"" + msg + "\"}";
    }

    @Note("获取注解自己配置的错误返回信息，如果是默认值那就返回 null")
    private String private_getNotHasUniqueIDErrorThrowMsg(QuotaLimit limit, String uniqueID) {
        int code = limit.nCode();
        String msg = limit.nMsg();
        if(code <= 0 || msg.isEmpty()) {
            return this.notHasUniqueIDErrorMessage.replace("#{uniqueID}", uniqueID);
        }
        return "{\"code\":" + code + ", \"message\":\"" + msg + "\"}";
    }
}
